#include "k_md5.h"
#include <stdio.h>


/* 大小端测试联合体 */
static union model
{
	unsigned int _i;
	char _c[sizeof(unsigned int)];
} model = {1};

/**
 * 大小端判断
 * @return : 如果为true则为小端，反之为大端
 */
static unsigned int bCheckBigSmallEndian()
{
	return 1 != model._c[0];
}

/* 四轮运算宏函数 */
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))    
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))

/* 四轮运算幻数 unsigned unsigned int(abs(sin(i+1))*(2pow32)) */
const static unsigned int ti[] = 
{
	0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
	0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
	0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
	0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
	
	0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
	0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
	0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
	0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
	
	0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
	0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
	0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
	0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
	
	0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
	0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
	0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
	0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};

/* 左移计数 */
const static unsigned int s[]=
{
	 7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
	 5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
     4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
	 6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21
};

/**
 * 初始化结果序列
 * @params : 4个需要填充幻数的引用a, b, c, d
 */
static void InitMagicNumTemp(unsigned int *a, unsigned int *b, unsigned int *c, unsigned int *d)
{
	if(bCheckBigSmallEndian())
	{/* 大端模式 */
		*a = 0x01234567;
		*b = 0x89abcdef;
		*c = 0xfedcba98;
		*d = 0x87654321;
	}
	else
	{/* 小端模式 */
		*a = 0x67452301;
		*b = 0xefcdab89;
		*c = 0x98badcfe;
		*d = 0x10325476;
	}
}


/**
 * 四轮运算
 * @params a, b, c, d: 存储序列化结果a, b, c, d的引用
 * @params M : 64bit的子序列
 */
static void Loop(unsigned int *a, unsigned int *b, unsigned int *c, unsigned int *d,
	unsigned int M[])
{
	unsigned int f, g, temp, i;
	unsigned int atemp;
	unsigned int btemp;
	unsigned int ctemp;
	unsigned int dtemp;
	atemp = *a;
	btemp = *b;
	ctemp = *c;
	dtemp = *d;
	
	for (i = 0; i < 64; i++)
	{
		if (i < 16)
		{
			f = F(btemp, ctemp, dtemp);
			/* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */
			g = i;
		}
		else if (i < 32)
		{
			f = G(btemp, ctemp, dtemp);
			/* 1 6 11 0 5 10 15 4 9 14 3 8 13 2 7 12 */
			g = (i * 5 + 1) % 16;
		}
		else if (i < 48)
		{
			f = H(btemp, ctemp, dtemp);
			/* 5 8 11 14 1 4 7 10 13 0 3 6 9 12 15 2 */
			g = (i * 3 + 5) % 16;
		}
		else
		{
			f = I(btemp, ctemp, dtemp);
			/* 0 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 */
			g = (i * 7 ) % 16;
		}
		temp  = dtemp;
        dtemp = ctemp;
        ctemp = btemp;
        btemp = btemp + (
			/* 循环左移 */
			(atemp + f + M[g] + ti[i]) << s[i] | 
			(atemp + f + M[g] + ti[i]) >> (32 - s[i])
			);
        atemp = temp;
	}
	*a = *a + atemp;
	*b = *b + btemp;
	*c = *c + ctemp;
	*d = *d + dtemp;
}

/**
 * 4bit的整数转换成16进制表示的字符
 * @params nBit : 需要转换的整形,会使用到前4个bit
 * @return : 转换后的Hex表示的字符
 */
static char Bit2Char(unsigned int nBit)
{
	static const char cHex[] = { "0123456789abcdef" };
	return cHex[nBit & 0x0F];
}


/**
 * 4字节整数转换成16进制字符串
 * @params nBit : 需要转换的4字节整形
 * @params cMD5 : 需要保存MD5值的字符串(传递保存位置指针)
 */
static void Int2Hex(unsigned int nBit, char *cMD5)
{
	unsigned int i;
	unsigned int bit8;
	for(i = 0; i < 4; i++)
	{
		/* 0x 00 00 00 00 */
		bit8 = (nBit >> i * 8) & 0xff;
		cMD5[ i * 2 ]     = Bit2Char(bit8 / 16);
		cMD5[ i * 2  + 1] = Bit2Char(bit8 % 16);
	}
}

/**
 * 以组为单位对链式变量（基数）进行MD5四轮法则计算
 * @params a, b, c, d: 存储序列化结果a, b, c, d的引用
 * @params cGroup : 切割的组
 */
static void CreateMD5ByGroup(unsigned int *a, unsigned int *b, unsigned int *c, unsigned int *d, const char *cGroup)
{
	unsigned int strByte[16];
	int nPos;
	for(nPos = 0; nPos < 64; nPos++)
	{/* nPos >> 2 其实为除于4，就是把4个字节的数据按照小端存储到int里面*/
		if(nPos < 16) { strByte[nPos] = 0; }
		strByte[nPos >> 2] |= ((unsigned char)cGroup[nPos]) << ((nPos % 4) * 8);
	}
	/* 四轮运算 */
	Loop(a, b, c, d, strByte);
}

/*******************************************************************************/

void MD5ForString(const char* cStringValue, unsigned long long llStringSize, char *cMD5)
{
	unsigned int a, b, c, d;
	unsigned long long llParseSize;
	char cEnd[65];
	int nPos;

	/* 基数初始化 */
	if(0 == cMD5) return ;
	InitMagicNumTemp(&a, &b, &c, &d);
	
	/* 数据获取 */
	llParseSize = 0;
	while(1)
	{
		if(llStringSize >= llParseSize + 64)
		{/* 直接获取一组数据(64位),进行位调整以及四轮运算 */
			CreateMD5ByGroup(&a, &b, &c, &d, cStringValue + llParseSize * 64);
			llParseSize = llParseSize + 64;
		}
		else
		{/* 读取到结尾，需要进行处理 */
			for(nPos = 0; nPos < llStringSize - llParseSize; nPos++)
			{/* 保存剩余数值 */
				cEnd[nPos] = cStringValue[nPos];
			}

			/* 补位，100...000直到64位 */
			cEnd[nPos++] = 0x80;
			for(; nPos < 65; nPos++) { cEnd[nPos] = 0; }

			/* 处理最后一个数组，判断是否直接补长度位进行四轮运算还是下一个数组再补长度进行四轮运算 */
			if((llStringSize - llParseSize) < 56) /* 填充必须进行，故不可取等于 */
			{/* 直接补长度进行四轮运算 */
				for(nPos = 0; nPos < 8; nPos++)
				{
					/* 255为0xff,按字节对长度进行小端存储*/
					cEnd[56 + nPos] = ((llStringSize * 8) & (255 << (nPos * 8))) >> (nPos * 8);
				}
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
			}
			else
			{/* 先进行四轮运算，下组才作为最后一组补长度进行四轮运算*/
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
				for(nPos = 0; nPos < 64; nPos++) 
				{/* 初始化数组cEnd */ 
					cEnd[nPos] = 0; 
				}
				for(nPos = 0; nPos < 8; nPos++)
				{
					/* 255为0xff,按字节对长度进行小端存储*/
					cEnd[56 + nPos] = ((llStringSize * 8) & (255 << (nPos * 8))) >> (nPos * 8);
				}
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
			}
			break;
		}
	}
	
	/* 保存生成MD5值 */
	Int2Hex(a, cMD5);
	Int2Hex(b, cMD5 + 8);
	Int2Hex(c, cMD5 + 16);
	Int2Hex(d, cMD5 + 24);
	cMD5[32] = 0;
}


void MD5ForFile(const char* cFileName, char *cMD5)
{
	unsigned int a, b, c, d;
	unsigned long long llStringSize;
	unsigned long long llParseSize;
	char cEnd[65];
	int nPos;
	FILE *pFile;
	cEnd[64] = 0;
	pFile = 0;

	/* 基数初始化 */
	if(0 == cMD5) return ;
	InitMagicNumTemp(&a, &b, &c, &d);
	
	/* 数据获取 */
	llStringSize = 0;
	llParseSize = 0;
	pFile = fopen(cFileName, "rb");
	if(!pFile) return;
	while(1)
	{
		nPos = fread(cEnd, 1, 64, pFile);

		if(nPos < 64)
		{/* 读取完毕 */
			llStringSize = llStringSize + nPos;
			
			/* 补位，100...000直到64位 */
			cEnd[nPos++] = 0x80;
			for(; nPos < 65; nPos++) { cEnd[nPos] = 0; }

			/* 处理最后一个数组，判断是否直接补长度位进行四轮运算还是下一个数组再补长度进行四轮运算 */
			if((llStringSize - llParseSize) < 56)
			{/* 直接补长度进行四轮运算 */
				for(nPos = 0; nPos < 8; nPos++)
				{
					/* 255为0xff,按字节对长度进行小端存储*/
					cEnd[56 + nPos] = ((llStringSize * 8) & (255 << (nPos * 8))) >> (nPos * 8);
				}
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
			}
			else
			{/* 先进行四轮运算，下组才作为最后一组补长度进行四轮运算*/
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
				for(nPos = 0; nPos < 64; nPos++) 
				{/* 初始化数组cEnd */ 
					cEnd[nPos] = 0; 
				}
				for(nPos = 0; nPos < 8; nPos++)
				{
					/* 255为0xff,按字节对长度进行小端存储*/
					cEnd[56 + nPos] = ((llStringSize * 8) & (255 << (nPos * 8))) >> (nPos * 8);
				}
				CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
			}
			break;
		}
		else if (nPos == 64)
		{/* 读完一个64位段 */
			CreateMD5ByGroup(&a, &b, &c, &d, cEnd);
			llParseSize = llParseSize + 64;
			llStringSize = llStringSize + 64;
		}
		else
		{
			break;
		}
	}
	fclose(pFile);
	
	/* 保存生成MD5值 */
	Int2Hex(a, cMD5);
	Int2Hex(b, cMD5 + 8);
	Int2Hex(c, cMD5 + 16);
	Int2Hex(d, cMD5 + 24);
	cMD5[32] = 0;
}